﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Windows.Input;
using Caliburn.Micro;
using ExpressionTreeLib.ViewModels;

namespace ExpressionTreeLib
{
    /// <summary>
    /// Represents a ViewModel of an Expression object
    /// </summary>
    public abstract class NodeViewModel : PropertyChangedBase
    {
        private string _name;
        private bool _isNull;
        private IServiceProvider _serviceProvider;
        private Expression _cachedExpression;

        /// <summary>
        /// Gets the name of ViewModel, be bound to tree view item header text.
        /// </summary>
        [Browsable(false)]
        public string Name
        {
            get { return _name; }
            internal set { _name = value; this.NotifyOfPropertyChange( ()=> Name); }
        }

        /// <summary>
        /// Gets children nodes of the ViewModel
        /// </summary>
        [Browsable(false)]
        public ObservableCollection<NodeViewModel> Children { get; private set; }

        /// <summary>
        /// Gets available commands of the ViewModel. Be bound to tree view item context menu
        /// </summary>
        [Browsable(false)]
        public List<ICommand> Commands { get; private set; }

        /// <summary>
        /// Gets service provider. Returns recursively parent service provider if one's service provider is null.
        /// It's considerable to use IocContainer here. 
        /// </summary>
        [Browsable(false)]
        public IServiceProvider ServiceProvider
        {
            get { return _serviceProvider ?? this.Parent.ServiceProvider; }
            set { _serviceProvider = value; }
        }

        public Type ExpressionReturnType
        {
            get { return this.Expression.Type; }
        }

        /// <summary>
        /// Gets or sets parent view model node of current.
        /// </summary>
        [Browsable(false)]
        public NodeViewModel Parent { get; set; }

        /// <summary>
        /// Gets the expression object of the ViewModel. Throws exception when expression could be created.
        /// </summary>
        [Browsable(false)]
        public Expression Expression
        {
            get
            {
                if (_cachedExpression == null)
                {
                    if (this.IsNull)
                    {
                        this.Name = "(null)";
                        return null;
                    }

                    _cachedExpression = CreateExpression();
                    this.Name = _cachedExpression.NodeType.ToString() + " " + _cachedExpression;
                }

                return this._cachedExpression;
            }
        }

        /// <summary>
        /// Gets or sets if this ViewModel represents a null expression node.
        /// </summary>
        public bool IsNull
        {
            get { return _isNull; }
            set
            {
                _isNull = value;
                this.NotifyOfPropertyChange( ()=>IsNull);
            }
        }

        /// <summary>
        /// Instantialize a NodeViewModel instance. It's be called when reading an expression tree or changing a node
        /// </summary>
        protected NodeViewModel()
        {
            this.Children = new ObservableCollection<NodeViewModel>();
            this.Commands = new List<ICommand>();
            this.Commands.Add(new DelegateCommand<object>(this.ChangeNode) { Name = "Change"});
            this.Commands.Add(new DelegateCommand<object>(this.GenerateExpressionCode) { Name = "Generate Code"});
        }

        /// <summary>
        /// Set this ViewModel as new created. It's only be called when changing a node.
        /// I don't like this method.
        /// </summary>
        protected virtual void SetAsNew()
        {
            // TODO: We can load expression tree to view model tree recursively by LoadExpressionNode, instead of getting it by ExpressionVisitor.
        }

        /// <summary>
        /// Change current node to another one
        /// </summary>
        /// <param name="obj"></param>
        protected virtual void ChangeNode(object obj)
        {
            var newNodeType = this.GetService<IRequestNewNodeType>().GetNewNodeType();
            if( newNodeType == null ) return;

            var newModelType = ExpressionNodeTypeMapping.FindMappingViewModelType(newNodeType);
            var newmodel = (NodeViewModel) Activator.CreateInstance(newModelType);
            newmodel.Name = newNodeType.Name.Replace("Expression", " ???");

            this.Parent.Children.Insert(this.Parent.Children.IndexOf(this), newmodel);
            newmodel.Parent = this.Parent;

            this.Parent.Children.Remove(this);
            this.Parent = null;
            newmodel.SetAsNew();
            newmodel.SelectMe();
        }

        /// <summary>
        /// Gets service from the ServiceProvider, recursively
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        protected T GetService<T>()
            where T : class 
        {
            return this.ServiceProvider.GetService(typeof (T)) as T;
        }

        protected bool StepRequest<T1, T2, T3>(ref T1 t1, ref T2 t2, ref T3 t3)
        {
            if( !this.GetService<IStepRequest<T1>>().Request(ref t1)) return false;
            if( !this.GetService<IStepRequest<T2>>().Request(ref t2)) return false;

            return this.GetService<IStepRequest<T3>>().LastRequest(ref t3);
        }

        /// <summary>
        /// Loads expression node information.
        /// </summary>
        /// <param name="expression"></param>
        public virtual void LoadExpressionNode(Expression expression)
        {
            this._cachedExpression = expression;
            this._name = expression.NodeType.ToString() + " " + expression;
        }

        /// <summary>
        /// Invalidate cached expression object.
        /// </summary>
        protected void InvalidateExpression()
        {
            this._cachedExpression = null;
        }

        /// <summary>
        /// Create expression based upon all information set on this model
        /// </summary>
        /// <returns></returns>
        protected abstract Expression CreateExpression();

        public override void NotifyOfPropertyChange(string propertyName)
        {
            base.NotifyOfPropertyChange(propertyName);

            if (propertyName == "Name") return;
            try
            {
                var par = this;
                while (par.Parent != null)
                {
                    par.InvalidateExpression();
                    par = par.Parent;
                }

                par.InvalidateExpression();
                var expression = par.Expression;
            }
            catch
            {
            }
        }

        /// <summary>
        /// Makes it as selected in tree view.
        /// </summary>
        protected void SelectMe()
        {
            this.GetService<ITreeViewService>().Select(this);
        }

        /// <summary>
        /// Reloads property grid control.
        /// </summary>
        protected void ReloadDetailView()
        {
            this.GetService<IDetailViewService>().Reload();
        }

        /// <summary>
        /// Recursively finds parameter view model with same name as this one.
        /// </summary>
        /// <param name="parameterName"></param>
        /// <returns>null if not found</returns>
        protected ParameterExpression FindReferenceParameter(string parameterName)
        {
            foreach (var nvm in this.Children)
            {
                var pvm = nvm as ParameterViewModel;
                if (pvm != null && pvm.ParameterName == parameterName)
                    return pvm.CreateExpression() as ParameterExpression;

                var parameterExpression = nvm.FindReferenceParameter(parameterName);
                if (parameterExpression != null) return parameterExpression;
            }

            return null;
        }

        private void GenerateExpressionCode(object obj)
        {
            var sb = new StringBuilder();
            this.GenerateCode(sb);
            this.GetService<IShowMessage>().Show(sb.ToString());
        }

        /// <summary>
        /// Generates C# code to make expression.
        /// </summary>
        /// <param name="stringBuilder"></param>
        public virtual void GenerateCode(StringBuilder stringBuilder)
        {
            if( this.IsNull )
                stringBuilder.Append("null");
        }

        protected void ClearChildren()
        {
            foreach (var child in this.Children.ToArray())
            {
                child.Parent = null;
                this.Children.Remove(child);
            }
        }

        /// <summary>
        /// Represents an empty node.
        /// </summary>
        public class EmptyViewModel : NodeViewModel
        {
            public EmptyViewModel()
            {
                this._name = "(null)";
                this._isNull = true;
            }
            protected override Expression CreateExpression()
            {
                throw new NotSupportedException();
            }

            public override void LoadExpressionNode(Expression expression)
            {
                
            }
        }
    }
}
